package com.study.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * gateway 集成 sentinel 实现限流
 */
@Slf4j
@Configuration
public class SentinelGatewayConfiguration {

    //视图解析器
    private final List<ViewResolver> viewResolvers;

    //http请求和响应数据的编码配置
    private final ServerCodecConfigurer serverCodecConfigurer;

    public SentinelGatewayConfiguration(
            ObjectProvider<List<ViewResolver>> listObjectProvider,
            ServerCodecConfigurer serverCodecConfigurer
    ) {
        this.viewResolvers = listObjectProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 限流异常处理器、限流异常出现时、执行这个方法
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        //默认返回错误异常 message code 429
        return new SentinelGatewayBlockExceptionHandler(
                this.viewResolvers,
                this.serverCodecConfigurer
        );
    }

    /**
     * 限流过滤器、是gateway 全局过滤器、优先级定义为最高
     *
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 初始化限流规则
     */

    //第三种方式：注释掉@ PostConstruct因为使用的是
    //gateway-flow-rule-api-sentinel.json和gateway-flow-rule-sentinel.json本地文件
    //@PostConstruct
    public void doInit() {

        log.info("-----------------------------------------");

        log.info("load sentinel gateway rules");
        initGatewayRules();

        //加载自定义限流异常处理器
        initBlockHandler();

        log.info("-----------------------------------------");
    }

    /**
     * 硬编码网关限流规则
     */
    public void initGatewayRules() {

        Set<GatewayFlowRule> rules = new HashSet<>();

        GatewayFlowRule flowRule = new GatewayFlowRule();

        //-------------------------------------------------
        //第一种方式
        //指定限流模式、根据route_id做限流、默认模式
        flowRule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        //指定 route_id -> service id
        //ecommerce-nacos-client 所有接口都适用
        flowRule.setResource("ecommerce-nacos-client");
        //按照QPS限流
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //统计窗口和限流阈值
        flowRule.setIntervalSec(60);
        flowRule.setCount(3);

        //rules.add(flowRule);
        //-------------------------------------------------

//        //第二种方式
//        //todo bug、没有起作用
//        //限流分组、sentinel 先去找规则定义、再去找规则中定义的分组
//
//        //规则定义
        rules.add(
                new GatewayFlowRule("nacos-client-api-1")
                .setCount(3).setIntervalSec(60)
        );
        rules.add(
                new GatewayFlowRule("nacos-client-api-2")
                        .setCount(3).setIntervalSec(60)
        );

        //-------------------------------------------------
        //加载到网关中 第一种方式和第二种方式都有
        GatewayRuleManager.loadRules(rules);

        //第二种方式 加载限流分组
        initCustomizedApis();

    }

    /**
     * 硬编码网关限流分组
     * 1、最大限制
     * 2、具体的分组
     */
    private void initCustomizedApis() {

        Set<ApiDefinition> definitions = new HashSet<>();

        //nacos-client-api 组、最大的限制
        ApiDefinition api = new ApiDefinition("nacos-client-api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    //模糊匹配 /ecommerce-nacos-client/ 及其子路径的所有请求
                    add(new ApiPathPredicateItem()
                            .setPattern("/ecommerce-nacos-client/**")
                            //根据前缀匹配
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});

        // nacos-client-api-1 分组
        ApiDefinition api1 = new ApiDefinition("nacos-client-api-1")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem()
                            //精确匹配
                            .setPattern("ecommerce-nacos-client/nacos-client/service-instance"));
                }});

        // nacos-client-api-2 分组
        ApiDefinition api2 = new ApiDefinition("nacos-client-api-2")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    //模糊匹配 /ecommerce-nacos-client/ 及其子路径的所有请求
                    add(new ApiPathPredicateItem()
                            //精确匹配
                            .setPattern("/ecommerce-nacos-client/nacos-client/" +
                                    "xxxxxxx"));
                }});

        definitions.add(api1);
        definitions.add(api2);

        //加载限流分组
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    /**
     * 自定义限流异常处理器
     */
    private void initBlockHandler() {

        //自定义blockRequestHandler
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {

            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {

                log.error("---trigger gateway sentinel rule-----------");

                HashMap<String, String> result = new HashMap<>();
                result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
                result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
                result.put("route", "ecommerce-nacos-client");

                return ServerResponse
                        .status(HttpStatus.TOO_MANY_REQUESTS)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(result));
            }
        };

        //设置自定义限流异常处理器
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }


}
